/*
	运行：sudo ./LcdPlayer
	编译：gcc LcdPlayer.c -o LcdPlayer -L lib -l wiringPi (需已安装wiringPi)
	by：WuSiYu
	tieba ID: laowu2012007
*/

#include <stdio.h>		//标准库
#include <stdlib.h>
#include <string.h>
#include <iconv.h>		//字符编码转换库
#include <sys/timeb.h>	//时间库
#include <wiringPi.h>	//rasPi的GPIO库


#define VIDEO_DATA_FILE "./video.data"	//视频数据文件地址
#define DELAY_BEFORE_START 2.4			//视频开始播放前的延时，根据你的播放器的音乐载入速度而定：
										//参考值：使用mplayer播放器时，Pi 1设为3.2，Pi 2设为2.4

#define LCD_RS 4	//显示屏控制线
#define LCD_RW 5
#define LCD_EN 1

#define D1 30		//显示屏数据线
#define D2 21
#define D3 22
#define D4 23
#define D5 24
#define D6 25
#define D7 26
#define D8 27


char UTF8_TO_GB2312_OUT_BUFF[255];
int THIS_LINE_DATA[768];

long should_played_frames=0, real_played_frames=0, jumped_frames=0;
int isDebug=0;

struct timeb tp;
FILE *DataFileFp;

/*===================================================================
功能：编码转换
输入：UTF8
输出：GB2312
====================================================================*/
int Code_Convert(char *from_charset,char *to_charset,char *inbuf,int inlen,char *outbuf,int outlen){
	iconv_t cd;
	int rc;
	char **pin = &inbuf;
	char **pout = &outbuf;

	cd = iconv_open(to_charset,from_charset);
	if (cd==0) return -1;
	memset(outbuf,0,outlen);
	if (iconv(cd,pin,&inlen,pout,&outlen)==-1) return -1;
	iconv_close(cd);
	return 0;
}

int UTF8_to_GB2312(char *inbuf,int inlen,char *outbuf,int outlen){
	return Code_Convert("utf-8","gb2312",inbuf,inlen,outbuf,outlen);
}

/*===================================================================
功能：总线写入
输入：十六进制数据
输出：无
====================================================================*/
void bus_write(unsigned char data){
	int t[10];
	int f=0,i=0,d=data;

	//进制转换
	for(i=0;i<8;i++){
		t[i]=data%2;
		data=data/2;
	}

	//输出
	digitalWrite(D1,t[0]);
	digitalWrite(D2,t[1]);
	digitalWrite(D3,t[2]);
	digitalWrite(D4,t[3]);
	digitalWrite(D5,t[4]);
	digitalWrite(D6,t[5]);
	digitalWrite(D7,t[6]);
	digitalWrite(D8,t[7]);
}
/*===================================================================
功能：检查LCD忙状态
输入：无
输出：lcd_busy为1时，忙，等待。lcd-busy为0时,闲，可写指令与数据。
====================================================================*/
void chk_busy(){//检查忙位
	digitalWrite(LCD_RS,0);
	digitalWrite(LCD_RW,1);
	digitalWrite(LCD_EN,1);
	bus_write(0xff);
	pinMode(D8, INPUT);
	while(digitalRead(D8));
	pinMode(D8, OUTPUT);
	digitalWrite(LCD_EN,0);
}
/*====================================================================
功能：写命令
输入：8位数据
输出：无
=====================================================================*/
void WriteCmd_LCD12864(unsigned char cmdcode){
	chk_busy();
	digitalWrite(LCD_RS,0);
	digitalWrite(LCD_RW,0);
	digitalWrite(LCD_EN,1);
	bus_write(cmdcode);
	digitalWrite(LCD_EN,0);
}
/*====================================================================
功能：写数据
输入：8位数据
输出：无
=====================================================================*/
void WriteData_LCD12864(unsigned char Dispdata){
	chk_busy();
	digitalWrite(LCD_RS,1);
	digitalWrite(LCD_RW,0);
	digitalWrite(LCD_EN,1);
	bus_write(Dispdata);
	digitalWrite(LCD_EN,0);
}
/*==========================================================================
功能：发送字符串
输入：地址，字符串
输出：无
===========================================================================*/
void WriteWord_LCD12864(unsigned char a,unsigned char *d){//向LCD指定位置发送一个字符串,长度64字符之内。
	unsigned char *s;

	UTF8_to_GB2312(d,strlen(d),UTF8_TO_GB2312_OUT_BUFF,255);
	s=UTF8_TO_GB2312_OUT_BUFF;

	WriteCmd_LCD12864(a);
	while(*s>0){
		WriteData_LCD12864(*s);
		s++;
	}
}
/*==========================================================================
功能：发送字符串2
输入：字符串
输出：无
===========================================================================*/
void WriteWord_LCD12864_2(unsigned char *d){//向LCD发送一屏字符串,长度64字符之内。
	int i=0;
	unsigned char *s;

	UTF8_to_GB2312(d,strlen(d),UTF8_TO_GB2312_OUT_BUFF,255);
	s=UTF8_TO_GB2312_OUT_BUFF;

	WriteCmd_LCD12864(0x80);
	while(*s>0){
		WriteData_LCD12864(*s);
		s++;
		i++;
		if(i==16){
			WriteCmd_LCD12864(0x90);
		}
		if(i==32){
			WriteCmd_LCD12864(0x88);
		}
		if(i==48){
			WriteCmd_LCD12864(0x98);
		}
	}
}
/*==========================================================================
功能：清屏
输入：颜色1，颜色2
输出：无
===========================================================================*/
void Clean_LCD12864(unsigned char c,unsigned char d){
	unsigned char l,r;
	WriteCmd_LCD12864(0x34);
	for(l=0;l<32;l++){
		WriteCmd_LCD12864(0x80+l);
		WriteCmd_LCD12864(0x80);
		for(r=0;r<16;r++){
			WriteData_LCD12864(c);
			WriteData_LCD12864(d);
		}
	}
}
/*==========================================================================
功能：初始化LCD
输入：无
输出：无
===========================================================================*/
void Init_LCD12864(void){			//初始化LCD屏
	pinMode(D1, OUTPUT);			//设置GPIO为输出模式
	pinMode(D2, OUTPUT);
	pinMode(D3, OUTPUT);
	pinMode(D4, OUTPUT);
	pinMode(D5, OUTPUT);
	pinMode(D6, OUTPUT);
	pinMode(D7, OUTPUT);
	pinMode(D8, OUTPUT);

	pinMode(LCD_RS, OUTPUT);
	pinMode(LCD_RW, OUTPUT);
	pinMode(LCD_EN, OUTPUT);

	WriteCmd_LCD12864(0x38);		//选择8bit数据流
	delay(20);
	WriteCmd_LCD12864(0x01);		//清除显示，并且设定地址指针为00H
	delay(20);
	WriteCmd_LCD12864(0x0c);		//开显示(无游标、不反白)
	delay(20);
}
/*==========================================================================
功能：将 全局数组THIS_LINE_DATA 中的一帧的数据显示在显示屏上
输入：无
输出：无
===========================================================================*/
void Display_OneFrame(void){
	unsigned char l,r;

	if(isDebug){
		ftime(&tp);
		printf("%d-", tp.millitm);
	}

	for(l=0;l<32;l++){
		WriteCmd_LCD12864(0x80+l);
		WriteCmd_LCD12864(0x80+1);
		for(r=0;r<6;r++){
			WriteData_LCD12864(THIS_LINE_DATA[12*l+r*2]);
			WriteData_LCD12864(THIS_LINE_DATA[12*l+r*2+1]);
		}
	}

	for(l=0;l<32;l++){
		WriteCmd_LCD12864(0x80+l);
		WriteCmd_LCD12864(0x88+1);
		for(r=0;r<6;r++){
			WriteData_LCD12864(THIS_LINE_DATA[12*32+12*l+r*2]);
			WriteData_LCD12864(THIS_LINE_DATA[12*32+12*l+r*2+1]);
		}
	}

	if(isDebug){
		ftime(&tp);
		printf("%d\n", tp.millitm);
	}
}
/*==========================================================================
功能：从文件里读取一帧并放入 全局数组THIS_LINE_DATA 中
输入：无
输出：无
===========================================================================*/
void Load_OneFrame(void){
	int i=0, s=0, value_number=0, n=0;
	char this_line_text[3200];
	char one_value_buf[3];

	if(isDebug){
		ftime(&tp);
		printf("%d-", tp.millitm);
	}

	if(!fgets(this_line_text,3200,DataFileFp)){
		printf("At EOL, abort\n");
		printf("%d frames jumped, Total %d frames. \n", jumped_frames, should_played_frames);
		printf("Average fps: %f\n", 30.0 * (should_played_frames - jumped_frames) / should_played_frames );
		exit(0);
	}

	if(isDebug){
		ftime(&tp);
		printf("%d-", tp.millitm);
	}

	n=strlen(this_line_text);

	for(i=0;i<n;i++){	//遍历这行的每个字符
		if(this_line_text[i]=='-'){
			THIS_LINE_DATA[value_number]=atoi(one_value_buf);
			one_value_buf[0]=' '; one_value_buf[1]=' '; one_value_buf[2]=' ';
			s=0;value_number++;
		}else{
			one_value_buf[s]=this_line_text[i];
			s++;
		}
	}
}
/*==========================================================================
功能：跳过一帧
输入：无
输出：无
===========================================================================*/
void Jump_OneFrame(void){
	char this_line_text[3200];
	fgets(this_line_text,3200,DataFileFp);
}
/*==========================================================================
功能：煮含树 o(￣ヘ￣o)
输入：无
输出：无
===========================================================================*/
int main (int args, char *argv[]){
	int time_last_frame_ms=0;
	long time_last_frame_s=0, later_ms;

	wiringPiSetup();
	Init_LCD12864();

	if(argv[1]){
		isDebug=1;
		printf("Start-ReadFile-Process-Display\n");
	}

	WriteCmd_LCD12864(0x01);
	WriteWord_LCD12864(0x80,"Badapple on     ");
	WriteWord_LCD12864(0x90,"rasPi + LCD12864");
	WriteWord_LCD12864(0x88,"                ");
	WriteWord_LCD12864(0x98,"       By WuSiYu");

	delay(DELAY_BEFORE_START*1000);

	WriteCmd_LCD12864(0x01);
	WriteCmd_LCD12864(0x36);
	Clean_LCD12864(0x00,0x00);
	WriteCmd_LCD12864(0x36);

	DataFileFp=fopen(VIDEO_DATA_FILE,"r");

	ftime(&tp);
	time_last_frame_s = tp.time;
	time_last_frame_ms = tp.millitm;

	while(1){

		ftime(&tp);

		if(time_last_frame_ms > tp.millitm){	//如果“进位”了
			later_ms = (tp.time - time_last_frame_s)*1000 - (time_last_frame_ms - tp.millitm);	//计算距上一帧过去的毫秒数
			should_played_frames += (later_ms/1000)*30 + (later_ms%1000)/33;					//计算此时“理论上”应该播放了多少帧

			time_last_frame_ms += (later_ms%1000) - (later_ms%1000)%33;							//更新上一帧播放时的时间
			time_last_frame_s = tp.time-1;

			if(time_last_frame_ms > 1000){
				time_last_frame_ms -= 990;
				time_last_frame_s++;
			}
		}else{
			if(tp.millitm-time_last_frame_ms >= 33 || tp.time-time_last_frame_s > 1){
				should_played_frames += (tp.time - time_last_frame_s)*30 + (tp.millitm - time_last_frame_ms)/33;
				time_last_frame_ms += (tp.millitm - time_last_frame_ms) - (tp.millitm - time_last_frame_ms)%33;
				time_last_frame_s = tp.time;
			}
		}

		if(should_played_frames > real_played_frames){
			while(should_played_frames > real_played_frames+1){
				Jump_OneFrame();
				real_played_frames++;
				jumped_frames++;
			}
			Load_OneFrame();
			Display_OneFrame();
			real_played_frames++;
		}

		delay(1);
	}
}
